TypeScript Transformerを使って任意の構文木を書き換えるシンプルなWebアプリの例
#TypeScript_Transformer #TypeScript #AST
やりたいこと
TypeScriptで書かれたWebアプリの構文木書き換えてビルドしたい。
なるべくミニマルなTypeScript Transformerの例を作成したい。
実際に動作するリポジトリ
このページの内容は以下のGitHubのリポジトリにある。
https://gh-card.dev/repos/nwtgck/typescript-transformer-prac-web.svg https://github.com/nwtgck/typescript-transformer-prac-web
npm run serverした後ににアクセスすれば動作が確認できる。
やりかた
今回の技術にあたっての貴重な日本語の資料「TypeScript Transformerについてのお話 - Qiita」。
上記の資料でも触れられているがtsconfig.jsonでCustom Transformersを指定すること現状ができない。
ttypescriptを使うことでtsconfig.jsonで指定できるようになるようだが変わったことをすると後でハマったり融通が利かなくなる経験則があるので避けた。
そこでWebpackでts-loaderを使う。ts-loaderはよく使うのでこれでできれば複雑になったときに変なことでハマることが少ないと思う。
まず、シンプルなTypeScript Transformerを作る。
上記の日本語記事を参考にalertをconsole.logに変換するTypeScript Transformerを作った。
code:transformers/my-transformer.ts
// (base: https://qiita.com/Quramy/items/1c9c2f7da2a6548f6901)
import * as ts from 'typescript';
// Transformer for alert("...") => console.log("alert: ...");
export default function (ctx: ts.TransformationContext) {
function visitNode(node: ts.Node): ts.Node {
if (!isAlert(node)) {
return ts.visitEachChild(node, visitNode, ctx);
}
return createHelper(node);
}
function createHelper(node: ts.Node) {
ctx.requestEmitHelper({
name: "ts:say",
priority: 0,
scoped: false,
text: "var __alert_console__ = function(msg) { console.log('alert: ' + msg); };",
});
return ts.setTextRange(ts.createIdentifier("__alert_console__" ), node);
}
function isAlert(node: ts.Node): node is ts.PropertyAccessExpression {
return node.kind === ts.SyntaxKind.Identifier && node.getText() === "alert";
}
return (source: ts.SourceFile) => ts.updateSourceFileNode(source, ts.visitNodes(source.statements, visitNode));
}
以下のようにtsc transformers/*.tsでtransformers/*.jsとして配置されるようにあらかじめビルドされるようにしている。
code:package.json
...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build-transformers": "tsc transformers/*.ts",
"serve": "npm run build-transformers && webpack-dev-server --watch",
"build": "npm run build-transformers && webpack"
....
あとはwebpack.config.jsで上記のTypeScript Transformerを使うように設定した。
code:webpack.config.js
// 自分のTransformerをインポート
const mytransformer = require('./transformers/my-transformer').default;
...
module.exports = {
...
module: {
rules: [
...
{
test: /\.ts$/,
exclude: /node_modules/,
loader: 'ts-loader',
// 以下を追記
options: {
getCustomTransformers: () => ({
before: mytransformer
}),
transpileOnly: true,
},
}
]
},
...
main.tsでalert()を使った何かを書く。
code:main.ts
...
alert("hello, world");
実際の変更のコミット
実際の動作
alert()しているがalertされずにコンソールに出力されていることが確認できる。
https://gyazo.com/d0f923de11ab7ccf6aee2fc81dd83c20
TypeScript Transformerを使えば、TypeScriptの型情報を元にしたASTの変換も書けるし、ブラウザで非対応の技術を対応している技術に変換して動かすようなこともできるし、色々できそう。
その他の参考にした資料
How to Write a TypeScript Transform (Plugin) - Doctor Evidence Development
TypeStrong/ts-loader: TypeScript loader for webpack
Igorbek/typescript-plugin-styled-components: TypeScript transformer for improving the debugging experience of styled-components
おまけ
「TypeScriptのASTを見る/デバッグするならTypeScript AST Viewerが良さそう」は開発の助けになりそう。
以下(おそらくソースマップ)を見るとalert()のままになっている。変換後のTypeScriptだとよかったのだが。これだとデバッグ時はすごく困難な気がする。
https://gyazo.com/1b0886897f10f16b0d533d7d925d016c